// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using Internal.NativeCrypto;

namespace System.Security.Cryptography
{
    public sealed partial class ECDsaCng : ECDsa
    {
        /// <summary>
        ///         ImportParameters will replace the existing key that ECDsaCng is working with by creating a
        ///         new CngKey. If the parameters contains only Q, then only a public key will be imported.
        ///         If the parameters also contains D, then a full key pair will be imported.
        ///         The parameters Curve value specifies the type of the curve to import.
        /// </summary>
        /// <exception cref="CryptographicException">
        ///     if <paramref name="parameters" /> does not contain valid values.
        /// </exception>
        /// <exception cref="NotSupportedException">
        ///     if <paramref name="parameters" /> references a curve that cannot be imported.
        /// </exception>
        /// <exception cref="PlatformNotSupportedException">
        ///     if <paramref name="parameters" /> references a curve that is not supported by this platform.
        /// </exception>
        public override void ImportParameters(ECParameters parameters)
        {
            parameters.Validate();
            ThrowIfDisposed();

            ECCurve curve = parameters.Curve;
            bool includePrivateParameters = parameters.D != null;
            bool hasPublicParameters = parameters.Q.X != null && parameters.Q.Y != null;

            if (curve.IsPrime)
            {
                if (!hasPublicParameters && includePrivateParameters)
                {
                    byte[] zero = new byte[parameters.D!.Length];
                    ECParameters ecParamsCopy = parameters;
                    ecParamsCopy.Q.X = zero;
                    ecParamsCopy.Q.Y = zero;
                    byte[] ecExplicitBlob = ECCng.GetPrimeCurveBlob(ref ecParamsCopy, ecdh: false);
                    ImportFullKeyBlob(ecExplicitBlob, includePrivateParameters: true);
                }
                else
                {
                    byte[] ecExplicitBlob = ECCng.GetPrimeCurveBlob(ref parameters, ecdh: false);
                    ImportFullKeyBlob(ecExplicitBlob, includePrivateParameters);
                }
            }
            else if (curve.IsNamed)
            {
                // FriendlyName is required; an attempt was already made to default it in ECCurve
                if (string.IsNullOrEmpty(curve.Oid.FriendlyName))
                    throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_InvalidCurveOid, curve.Oid.Value!.ToString()));

                if (!hasPublicParameters && includePrivateParameters)
                {
                    byte[] zero = new byte[parameters.D!.Length];
                    ECParameters ecParamsCopy = parameters;
                    ecParamsCopy.Q.X = zero;
                    ecParamsCopy.Q.Y = zero;
                    byte[] ecNamedCurveBlob = ECCng.GetNamedCurveBlob(ref ecParamsCopy, ecdh: false);
                    ImportKeyBlob(ecNamedCurveBlob, curve.Oid.FriendlyName, includePrivateParameters: true);
                }
                else
                {
                    byte[] ecNamedCurveBlob = ECCng.GetNamedCurveBlob(ref parameters, ecdh: false);
                    ImportKeyBlob(ecNamedCurveBlob, curve.Oid.FriendlyName, includePrivateParameters);
                }
            }
            else
            {
                throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_CurveNotSupported, curve.CurveType.ToString()));
            }
        }

        /// <summary>
        ///     Exports the key and explicit curve parameters used by the ECC object into an <see cref="ECParameters"/> object.
        /// </summary>
        /// <exception cref="CryptographicException">
        ///     if there was an issue obtaining the curve values.
        /// </exception>
        /// <exception cref="PlatformNotSupportedException">
        ///     if explicit export is not supported by this platform. Windows 10 or higher is required.
        /// </exception>
        /// <returns>The key and explicit curve parameters used by the ECC object.</returns>
        public override ECParameters ExportExplicitParameters(bool includePrivateParameters)
        {
            return ECCng.ExportExplicitParameters(Key, includePrivateParameters);
        }

        /// <summary>
        ///     Exports the key used by the ECC object into an <see cref="ECParameters"/> object.
        ///     If the curve has a name, the Curve property will contain named curve parameters otherwise it will contain explicit parameters.
        /// </summary>
        /// <exception cref="CryptographicException">
        ///     if there was an issue obtaining the curve values.
        /// </exception>
        /// <returns>The key and named curve parameters used by the ECC object.</returns>
        public override ECParameters ExportParameters(bool includePrivateParameters)
        {
            return ECCng.ExportParameters(Key, includePrivateParameters);
        }

        public override void ImportPkcs8PrivateKey(ReadOnlySpan<byte> source, out int bytesRead)
        {
            ThrowIfDisposed();

            CngPkcs8.Pkcs8Response response = CngPkcs8.ImportPkcs8PrivateKey(source, out int localRead);

            ProcessPkcs8Response(response);
            bytesRead = localRead;
        }

        public override void ImportEncryptedPkcs8PrivateKey(
            ReadOnlySpan<byte> passwordBytes,
            ReadOnlySpan<byte> source,
            out int bytesRead)
        {
            ThrowIfDisposed();

            CngPkcs8.Pkcs8Response response = CngPkcs8.ImportEncryptedPkcs8PrivateKey(
                passwordBytes,
                source,
                out int localRead);

            ProcessPkcs8Response(response);
            bytesRead = localRead;
        }

        public override void ImportEncryptedPkcs8PrivateKey(
            ReadOnlySpan<char> password,
            ReadOnlySpan<byte> source,
            out int bytesRead)
        {
            ThrowIfDisposed();

            CngPkcs8.Pkcs8Response response = CngPkcs8.ImportEncryptedPkcs8PrivateKey(
                password,
                source,
                out int localRead);

            ProcessPkcs8Response(response);
            bytesRead = localRead;
        }

        private void ProcessPkcs8Response(CngPkcs8.Pkcs8Response response)
        {
            // Wrong algorithm?
            switch (response.GetAlgorithmGroup())
            {
                // CNG ECDH and ECDSA keys can both do ECDSA.
                case BCryptNative.AlgorithmName.ECDsa:
                case BCryptNative.AlgorithmName.ECDH:
                    AcceptImport(response);
                    return;
            }

            response.FreeKey();
            throw new CryptographicException(SR.Cryptography_NotValidPublicOrPrivateKey);
        }

        public override byte[] ExportEncryptedPkcs8PrivateKey(
            ReadOnlySpan<byte> passwordBytes,
            PbeParameters pbeParameters)
        {
            ArgumentNullException.ThrowIfNull(pbeParameters);

            return CngPkcs8.ExportEncryptedPkcs8PrivateKey(
                this,
                passwordBytes,
                pbeParameters);
        }

        public override byte[] ExportEncryptedPkcs8PrivateKey(
            ReadOnlySpan<char> password,
            PbeParameters pbeParameters)
        {
            ArgumentNullException.ThrowIfNull(pbeParameters);

            PasswordBasedEncryption.ValidatePbeParameters(
                pbeParameters,
                password,
                ReadOnlySpan<byte>.Empty);

            if (CngPkcs8.IsPlatformScheme(pbeParameters))
            {
                return ExportEncryptedPkcs8(password, pbeParameters.IterationCount);
            }

            return CngPkcs8.ExportEncryptedPkcs8PrivateKey(
                this,
                password,
                pbeParameters);
        }

        public override bool TryExportEncryptedPkcs8PrivateKey(
            ReadOnlySpan<byte> passwordBytes,
            PbeParameters pbeParameters,
            Span<byte> destination,
            out int bytesWritten)
        {
            ArgumentNullException.ThrowIfNull(pbeParameters);

            PasswordBasedEncryption.ValidatePbeParameters(
                pbeParameters,
                ReadOnlySpan<char>.Empty,
                passwordBytes);

            return CngPkcs8.TryExportEncryptedPkcs8PrivateKey(
                this,
                passwordBytes,
                pbeParameters,
                destination,
                out bytesWritten);
        }

        public override bool TryExportEncryptedPkcs8PrivateKey(
            ReadOnlySpan<char> password,
            PbeParameters pbeParameters,
            Span<byte> destination,
            out int bytesWritten)
        {
            ArgumentNullException.ThrowIfNull(pbeParameters);

            PasswordBasedEncryption.ValidatePbeParameters(
                pbeParameters,
                password,
                ReadOnlySpan<byte>.Empty);

            if (CngPkcs8.IsPlatformScheme(pbeParameters))
            {
                return TryExportEncryptedPkcs8(
                    password,
                    pbeParameters.IterationCount,
                    destination,
                    out bytesWritten);
            }

            return CngPkcs8.TryExportEncryptedPkcs8PrivateKey(
                this,
                password,
                pbeParameters,
                destination,
                out bytesWritten);
        }
    }
}
